#include "Physics.h"
#include "Game.h"
#include "Constants.h"
#include "Messenger.h"
#include "Lang.h"
#include <math.h>

#define COLLIDE_TOP	0x01
#define COLLIDE_BOTTOM	0x00
#define COLLIDE_LEFT	0x12
#define COLLIDE_RIGHT	0x02

int intersect_rect (SDL_Rect * rect1, SDL_Rect * rect2)
{


}

/* Give a flick to the ball when it is
 * stucked in a fixed direction.
 */
void flick (void)
{

	double angle;

	game.flicks--;

	message ("!!! OUCH !!!");

	/* No more flicks available */
	if ( game.flicks < 0 ) return;

	angle = M_PI + (M_PI/2.0) * rand() / (RAND_MAX + 1.0);

	game.balldy = cos(angle);
	game.balldx = -sin(angle);

}

void move_ball (Sint16 newx, Sint16 newy)
{
	game.ballx_old = game.ballx;
	game.bally_old = game.bally;
	game.ballx = newx + game.speed*game.balldx;
	game.bally = newy + game.speed*game.balldy;

	game.ballx_i = (int)(game.ballx);
	game.bally_i = (int)(game.bally);
}

int check_collisions_step (Sint16 next_x, Sint16 next_y)
{

	SDL_Rect ballrect;
	SDL_Rect rect;
	int dir;

	ballrect.x = next_x;
	ballrect.y = next_y;
	ballrect.w = game.ball->w;
	ballrect.h = game.ball->h;

	/* ********************** */
	/* Collision with the pin */
	/* ********************** */
	if ( (dir = intersect_rect(&ballrect, &(game.pinrect))) )

		double t_ballx; 
		double angle;

		/* Touched the pin */
		/* Calculate the new vector */
		
		/* Translate */
		t_ballx = next_x - game.pinrect.x;

		/* Calculate the new angle of the ball */
		angle = M_PI * (t_ballx + (double)game.ball->w/2) / (double)game.pinrect.w;

		/* Beware: screen coordinates are at the top left so
		   the sign of dy must be inverted. */
		game.balldx = -cos(angle);
		if ( dir & COLLIDE_TOP )
			game.balldy = -sin(angle);
		else if ( dir & COLLIDE_BOTTOM ) /* Special critical case */
			game.balldy = sin(angle);
			
		bip();
		
		/* Avoid dirty blit artefacts */
		blit_pin(game.pinrect.x);

		/* If the edge size is greater than zero, do not return to
 		   check double collisions */
		if ( game.lvl.edgesize <= 0 ) 
		{
			touched = 1;
			break;
		}
	}

	if ( game.lvl.edgesize > 0 )
	{
		/* ********************** */
		/* Collision with an edge */
		/* ********************** */

		/* Similar algorithm as for the 1 brick case */
		if ( next_y + game.ball->h >= game.viewport.bottom - EDGE_HEIGHT)
		{


			/* If a collision with an edge occured,
 			   we use the current ball position to determine its orientation
			   respectively to the edge */

			/* Left edge collision */
			if ( next_x < game.lvl.edgesize )
			{
				/* Top collision */
				if ( game.bally_i + game.ball->h < game.viewport.bottom - EDGE_HEIGHT )
				{
					game.balldy *= -1.0;
					touched = 1;
				}

				/* Right collision */
				else if ( game.ballx_i >= game.lvl.edgesize )
				{
					game.balldx *= -1.0;
					touched = 1;
				}
				
			}
			/* Right edge collision */
			else if ((next_x+game.ball->w) >= game.viewport.rect.w - game.lvl.edgesize )
			{
		
				/* Top collision */
				if ( game.bally_i + game.ball->h < game.viewport.bottom - EDGE_HEIGHT )
				{
					game.balldy *= -1.0;
					touched = 1;
				}

				/* Left collision */
				else if ( game.ballx_i + game.ball->w < game.viewport.rect.w - game.lvl.edgesize )
				{
					game.balldx *= -1.0;
					touched = 1;
				}
			}

			if ( touched )
			{
				/* Avoid dirty blit artefacts */
				blit_edges();
				bip();
				break;
			}

		}
		
	}	

	/* *************************************** */
	/* Collision with the border of the screen */
	/* *************************************** */
	if ( (next_x <= 0) ||
	     (next_x + game.ball->w >= game.screen->w) )
	{
		game.balldx *= -1.0;
		touched = 1;
		bip();
	}
	if ( (next_y <= game.viewport.top) )
	{
		game.balldy *= -1.0;
		touched = 1;
		bip();
	}
	
	/*if ( next_y + game.ball->h >= game.viewport.bottom)*/
	/* Give a higher chance to pick up the ball */
	if ( next_y >= game.viewport.bottom )
	{


		if (rand() / (RAND_MAX + 1.0) <= LUCK_LOST)
		{
			message (STR_LUCK);
			flash(255,255,255, 1, 20);
			game.balldy *= -1.0;
			touched = 1;
			bip();
		}
		else if (SDL_GetTicks() - game.attempt_tick < 2000)
		{
			message (STR_BALL_SAVED);
			flash(0,255,255, 3, 5);
			game.balldy *= -1.0;
			touched = 1;	
			bip();
		}
		else {
			/* ball has been lost */
			game.lost = 1;
			game.balldx = game.balldy = 0;
			return;
		}

	}


	/* ************************* */
	/* Collision with the bricks */
	/* ************************* */
	if ( (next_x + game.ball->w >= OFX - game.balldx) &&
	     (next_x <= OFX + 16*(BRICK_W+BRICK_PADX) + game.balldx) &&
	     (next_y + game.ball->h >= OFY - game.balldy) &&
	     (next_y <= OFY + 8*(BRICK_H+BRICK_PADY) + game.balldy)
	   )
	{
	
		SDL_Rect ball;
		SDL_Rect bricks[4];
		int n_bricks;
		int x, y, k;
		
		/* 8 Hotpoints system
 		 *
 		 * 0--4--1
 		 * |	 |
 		 * 5	 6
 		 * |	 |
 		 * 2--7--3
 		 */

		/* Hotpoints */
		Sint16 hpx[8], hpy[8];

		/* Ball rectangle */
		ball.x = next_x;
		ball.y = next_y;
		ball.w = game.ball->w;
		ball.h = game.ball->h;

		/* Hotpoints coordinates */
		hpx[0] = ball.x;
		hpx[1] = ball.x + ball.w;
		hpx[2] = ball.x;
		hpx[3] = ball.x + ball.w;
		hpy[0] = ball.y;
		hpy[1] = ball.y;
		hpy[2] = ball.y + ball.h;
		hpy[3] = ball.y + ball.h;


		hpx[4] = ball.x + ball.w/2;
		hpx[5] = ball.x;
		hpx[6] = ball.x + ball.w;
		hpx[7] = ball.x + ball.w/2;
		hpy[4] = ball.y;
		hpy[5] = ball.y + ball.h/2;
		hpy[6] = ball.y + ball.h/2;
		hpy[7] = ball.y + ball.h;

		n_bricks = 0;


		/* Brick hashcode: y*HASH+x */
		int codes[8], code;
		int skip;
		int j, n;
		int HASH = 32;

		n = 0;
		void * ptr;
		long score_vals[3] = { SCORE_1_BRICK,
				       SCORE_2_BRICKS,
				       SCORE_3_BRICKS};
		int score_k = 0;
		long score  = 0; /* Don't remove this initialization ! */

		/* Check the collisions with the bricks at each hotpoint */
		for ( k = 0; k < 8; k++ )
		{

			/* Retrieve the brick position in the grid */
			x = (hpx[k] - OFX) / (BRICK_W + BRICK_PADX);
			y = (hpy[k] - OFY) / (BRICK_H + BRICK_PADY);
		
			/* Clip to the grid size */
			if ( x < 0 || x > 15 || y < 0 || y > 7 )
				continue;
			
			/* Avoid counting a brick more than once */
			code = y*HASH+x;
			skip = 0;

			for ( j = 0; j < n_bricks; j++ )
			{
				/* The brick has already been considered, skip it */
				if ( codes[j] == code )
				{
					skip = 1;
					break;
				}				
			}
			
			/* Brick already considered */
			if ( skip ) continue;
			
			/* If there is a brick at this position (not already destroyed) */
			if ( game.lvl.bricks[x][y] > '0' )
			{

				/* It's a destroyable brick */
				if ( game.lvl.bricks[x][y] <= '3' ) 
				{
					game.lvl.bricks[x][y] --;
					game.lvl.nhits --;
					score = score_vals[score_k++];
				}

				bip();

				/* Rectangle surrounding the brick */
				bricks[n_bricks].x =
					OFX + x * (BRICK_W + BRICK_PADX);
				bricks[n_bricks].y =
					OFY + y * (BRICK_H + BRICK_PADY);
				bricks[n_bricks].w = BRICK_W;
				bricks[n_bricks].h = BRICK_H;

				/* Hashcode of the brick */
				codes[n_bricks] = code;
				n_bricks ++;
				
			}
		}


		/* Fast return when no brick collide */
		if ( n_bricks != 0 ) touched = 1;
		else		     break;

		/* Increase the score */
		score_incr(score);

		/* Discriminant for the 2 bricks case */
		int discr;

		switch (n_bricks)
		{

			/* Collision with one brick: check the direction of the collision
 			   (top,right,bottom,left) */
			case 1:
				
				/* Consider the current position of the ball for the direction
				   checking */
				ball.x = game.ballx_i;
				ball.y = game.bally_i;

				if ( (ball.x > (bricks[0].x+bricks[0].w)))
				/* Right collision */
				{
					game.balldx *= -1.0;
				}
				if ( (ball.x+ball.w < bricks[0].x))
					/* Left collision */
				{
					game.balldx *= -1.0;
				}

				if ( (ball.y > (bricks[0].y+bricks[0].h)))
					/* Bottom collision */
				{
					game.balldy *= -1.0;
				}
				if ( (ball.y+ball.h < bricks[0].y))
					/* Top collision */
				{
					game.balldy *= -1.0;
				}
			
			break;

			/* Collision with two bricks.
 			   3 possible cases:
				- The bricks are aligned horizontally (invert y direction)
				- The bricks are aligned vertically   (invert x direction)
				- The bricks are not aligned, forming
					an angle.		      (invert both directions)

			   The discriminant is calculated by taking the absolute value of the
			   difference between each brick's code.

		           If the difference is one, then the bricks are aligned horizontally.
			   If it is equal to the hash value (HASH), then there're aligned vertically.
			   Else, they form an angle.
			*/
			case 2:

				discr = codes[1] - codes[0];
				discr = (discr < 0 ) ? (-discr) : (discr);

				if ( discr == 1 )
					game.balldy *= -1.0;
				else if (discr == HASH )
					game.balldx *= -1.0;
				else
				{
					game.balldx *= -1.0;
					game.balldy *= -1.0;
				}
				
			break;

			/* Collision with three bricks.
 			   The four possible cases are the same: invert both directions.
			*/
			case 3:

				game.balldx *= -1.0;
				game.balldy *= -1.0;
				
			break;

			default:
				printf ("Collision with %d bricks cannot occur in an 3D euclidean space.\n",
					n_bricks);

		}

		if ( game.lvl.nhits == 0 )
			game.won = 1;
	
		blit_bricks();
		message_status();			
	}

}

/** **************** */
/** Collision engine */
/** **************** */
/* Experimental projection routine (incremental) */
void check_collisions (void)
{
	
	double next_x, next_y;
	double last_x, last_y;
	int touched = 0;
	

	/* Where the ball will be at the next step of movement */
	last_x = game.ballx + game.speed*game.balldx;
	last_y = game.bally + game.speed*game.balldy;
	
	for ( next_x = game.ballx, next_y = game.bally;
	      (ABS(next_x-last_x) >= game.balldx) && (ABS(next_y-last_y) >= game.balldy);
	      next_x += game.balldx, next_y += game.balldy) 
	{

		touched = check_collisions_step(next_x, next_y);
		if ( touched ) break;
	}

	

	if ( touched ) move_ball (next_x, next_y);
	else	       move_ball (game.ballx, game.bally);

}

void physics_update (void)
{

	check_collisions();

}

